Welcome to my first own project including python code. This project seeks to give an overview of the largest ETFs provided by the most common ETF suppliers. Personally, I invest small amounts into ETF since the beginning of 2018. Now, I would like to examine, if my choices have been evolved as good as the picked ETF. Furthermore, the scope tries to shed some light into optimal weighting approaches, such that, this notebook tries to perform several optimization methods. Finally, I would like to simulate different portfolio values, if I would have invested monthly a certain amount.
This notebook is only personal motivated and will certainly lack at many point. Diversification will not be covered, since picking the largest ETFs according to their Net Assets does not include any deaper strategy.
# import needed packages
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from PIL import Image
import pandas_datareader.data as web
import datetime
import yfinance as yf
from matplotlib import dates
from pypfopt.expected_returns import mean_historical_return
from pypfopt.risk_models import CovarianceShrinkage
from pypfopt.efficient_frontier import EfficientFrontier
from pypfopt import plotting
import dash
from dash import html, dcc
import jupyterthemes as jt
from jupyterthemes import get_themes
from jupyterthemes.stylefx import set_nb_theme
set_nb_theme('oceans16')
Step 1: Load the data.
Step 2: Give an overview about the data and the performance.
Step 3: Perform portfolio optimization.
Step 4: Create an interactive dashboard for clean presentation.
IVV: iShares Core S&P 500 ETF
IEFA: iShares Core MSCI EAFE ETF (Europe, Australasia, Far East)
AGG: iShares Core U.S. Aggregate Bond ETF (fixed income securities)
IJR: iShares Core S&P Small-Cap ETF
SPX.MI: Lyxor S&P 500 UCITS ETF
WLDC.MI: Lyxor MSCI World UCITS ETF Acc
USRI.PA: Amundi Index Solutions - Amundi MSCI USA SRI UCITS ETF (track the performance of MSCI USA SRI Filtered ex Fossil Fuels Index)
MEUD.PA: Lyxor Core STOXX Europe 600
URTH: iShares MSCI World ETF
AEEM.PA: Amundi Index Solutions - Amundi MSCI Emerging Markets
IUHE.AS: iShares V PLC - iShares S&P 500 Health Care Sector UCITS ETF
ICOM.L: iShares Diversified Commodity Swap UCITS ETF
# Step 1 - importing data by defining a for loop that gets the tickers from a list
start = datetime.datetime(2018, 1, 1)
end = datetime.datetime.today()
ticker_ishares = ['IVV', 'IEFA', 'AGG', 'IJR']
ticker_amundi = ['SPX.MI', 'WLDC.MI', 'USRI.PA', 'MEUD.PA']
antons_ticker = ['URTH', 'AEEM.PA', 'IUHE.AS', 'ICOM.L']
data_ishares = pd.DataFrame(columns=ticker_ishares)
data_amundi = pd.DataFrame(columns=ticker_amundi)
antons_data = pd.DataFrame(columns=antons_ticker)
for i in ticker_ishares:
data_ishares[i] = yf.download(i, start, end)['Adj Close']
for i in ticker_amundi:
data_amundi[i] = yf.download(i, start, end)['Adj Close']
for i in antons_ticker:
antons_data[i] = yf.download(i, start, end)['Adj Close']
[*********************100%***********************] 1 of 1 completed [*********************100%***********************] 1 of 1 completed [*********************100%***********************] 1 of 1 completed [*********************100%***********************] 1 of 1 completed [*********************100%***********************] 1 of 1 completed [*********************100%***********************] 1 of 1 completed [*********************100%***********************] 1 of 1 completed [*********************100%***********************] 1 of 1 completed [*********************100%***********************] 1 of 1 completed [*********************100%***********************] 1 of 1 completed [*********************100%***********************] 1 of 1 completed [*********************100%***********************] 1 of 1 completed
# Step 2- descriptive statistic of the data set
data = [data_ishares, data_amundi, antons_data]
for i in data:
print('This shows the descriptive statistics for {}'.format(i.columns))
describe = i.describe().transpose()
display(np.round(describe, 2))
This shows the descriptive statistics for Index(['IVV', 'IEFA', 'AGG', 'IJR'], dtype='object')
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| IVV | 1221.0 | 332.42 | 72.28 | 214.95 | 266.94 | 311.80 | 403.07 | 474.32 |
| IEFA | 1221.0 | 60.20 | 7.41 | 40.19 | 55.33 | 57.91 | 66.66 | 75.18 |
| AGG | 1221.0 | 105.04 | 6.77 | 93.42 | 98.31 | 105.92 | 111.81 | 114.53 |
| IJR | 1221.0 | 85.24 | 16.43 | 47.15 | 72.94 | 79.72 | 102.93 | 118.39 |
This shows the descriptive statistics for Index(['SPX.MI', 'WLDC.MI', 'USRI.PA', 'MEUD.PA'], dtype='object')
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| SPX.MI | 1232.0 | 31.03 | 6.49 | 21.28 | 25.41 | 29.11 | 37.95 | 43.40 |
| WLDC.MI | 349.0 | 10.95 | 0.43 | 9.88 | 10.64 | 10.86 | 11.30 | 11.84 |
| USRI.PA | 1055.0 | 69.17 | 15.45 | 44.04 | 55.85 | 65.48 | 84.30 | 98.10 |
| MEUD.PA | 1232.0 | 166.58 | 21.41 | 114.63 | 150.76 | 159.00 | 185.63 | 212.45 |
This shows the descriptive statistics for Index(['URTH', 'AEEM.PA', 'IUHE.AS', 'ICOM.L'], dtype='object')
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| URTH | 1221.0 | 99.36 | 18.17 | 64.65 | 83.33 | 94.18 | 115.53 | 134.88 |
| AEEM.PA | 1211.0 | 4.41 | 0.47 | 3.16 | 4.07 | 4.27 | 4.80 | 5.48 |
| IUHE.AS | 603.0 | 6.22 | 0.52 | 5.00 | 5.80 | 6.34 | 6.61 | 7.20 |
| ICOM.L | 1197.0 | 5.50 | 1.08 | 3.72 | 4.86 | 5.15 | 5.97 | 8.65 |
# plotting the absolute change over time for the data
# set style
sns.set_style('darkgrid')
sns.set_palette('colorblind')
fig, ax = plt.subplots(figsize=(10,10), dpi=200, nrows= 3)
ax[0].plot(data_ishares['IVV'], label="iShares Core S&P 500", lw=2)
ax[0].plot(data_ishares['IEFA'], label=" iShares Core MSCI EAFE", lw=2)
ax[0].plot(data_ishares['AGG'], label="iShares Core U.S. Aggregate Bond",lw=2)
ax[0].plot(data_ishares['IJR'], label="iShares Core S&P Small-Cap",lw=2)
ax[1].plot(data_amundi['SPX.MI'], label="Lyxor S&P 500 UCITS", lw=2)
ax[1].plot(data_amundi['WLDC.MI'], label="Lyxor MSCI World UCITS", lw=2)
ax[1].plot(data_amundi['USRI.PA'], label="Amundi Index Solutions",lw=2)
ax[1].plot(data_amundi['MEUD.PA'], label="Lyxor Core STOXX Europe 600",lw=2)
ax[2].plot(antons_data['URTH'], label=" iShares MSCI World", lw=2)
ax[2].plot(antons_data['AEEM.PA'], label="Amundi MSCI Emerging Markets", lw=2)
ax[2].plot(antons_data['IUHE.AS'], label="iShares S&P 500 Health Care",lw=2)
ax[2].plot(antons_data['ICOM.L'], label="iShares Diversified Commodity Swap",lw=2)
ax[0].set_xlabel('Time')
ax[1].set_xlabel('Time')
ax[2].set_xlabel('Time')
ax[0].set_ylabel('Adjusted Close')
ax[1].set_ylabel('Adjusted Close')
ax[2].set_ylabel('Adjusted Close')
ax[0].set_title('Absolute change of the 4 largest ETFs provided by iShares according to their Net Assets in Mio.')
ax[1].set_title('Absolute change of the 4 largest ETFs provided by Amundi according to their Net Assets in Mio.')
ax[2].set_title('Absolute change of the my own 4 ETFs')
fig.suptitle('Absolute change for all 3 categories of ETFs seperatly plotted', fontsize=20)
ax[0].xaxis.set_major_locator(dates.YearLocator())
ax[0].xaxis.set_major_formatter(dates.DateFormatter("%Y"))
ax[0].tick_params(axis="x", which="major",rotation=0, pad=5)
ax[1].xaxis.set_major_locator(dates.YearLocator())
ax[1].xaxis.set_major_formatter(dates.DateFormatter("%Y"))
ax[1].tick_params(axis="x", which="major",rotation=0, pad=5)
ax[2].xaxis.set_major_locator(dates.YearLocator())
ax[2].xaxis.set_major_formatter(dates.DateFormatter("%Y"))
ax[2].tick_params(axis="x", which="major",rotation=0, pad=5)
ax[0].legend(loc ='upper left')
ax[1].legend(loc = 'upper left')
ax[2].legend(loc = 'upper left')
plt.tight_layout()
plt.show();
# plotting the relative change over time for the data
# set style
sns.set_style('darkgrid')
sns.set_palette('colorblind')
fig, ax = plt.subplots(figsize=(10,10), dpi=200, nrows= 3)
ax[0].plot(data_ishares['IVV'].pct_change().cumsum(), label="iShares Core S&P 500", lw=2)
ax[0].plot(data_ishares['IEFA'].pct_change().cumsum(), label=" iShares Core MSCI EAFE", lw=2)
ax[0].plot(data_ishares['AGG'].pct_change().cumsum(), label="iShares Core U.S. Aggregate Bond",lw=2)
ax[0].plot(data_ishares['IJR'].pct_change().cumsum(), label="iShares Core S&P Small-Cap",lw=2)
ax[1].plot(data_amundi['SPX.MI'].pct_change().cumsum(), label="Lyxor S&P 500 UCITS", lw=2)
ax[1].plot(data_amundi['WLDC.MI'].pct_change().cumsum(), label="Lyxor MSCI World UCITS", lw=2)
ax[1].plot(data_amundi['USRI.PA'].pct_change().cumsum(), label="Amundi Index Solutions",lw=2)
ax[1].plot(data_amundi['MEUD.PA'].pct_change().cumsum(), label="Lyxor Core STOXX Europe 600",lw=2)
ax[2].plot(antons_data['URTH'].pct_change().cumsum(), label=" iShares MSCI World", lw=2)
ax[2].plot(antons_data['AEEM.PA'].pct_change().cumsum(), label="Amundi MSCI Emerging Markets", lw=2)
ax[2].plot(antons_data['IUHE.AS'].pct_change().cumsum(), label="iShares S&P 500 Health Care",lw=2)
ax[2].plot(antons_data['ICOM.L'].pct_change().cumsum(), label="iShares Diversified Commodity Swap",lw=2)
ax[0].set_xlabel('Time')
ax[1].set_xlabel('Time')
ax[2].set_xlabel('Time')
ax[0].set_ylabel('Adjusted Close')
ax[1].set_ylabel('Adjusted Close')
ax[2].set_ylabel('Adjusted Close')
ax[0].set_title('Relative change of the 4 largest ETFs provided by iShares according to their Net Assets in Mio.')
ax[1].set_title('Relative change of the 4 largest ETFs provided by Amundi according to their Net Assets in Mio.')
ax[2].set_title('Relative change of the my own 4 ETFs')
fig.suptitle('Relative change for all 3 categories of ETFs seperatly plotted', fontsize=20)
ax[0].xaxis.set_major_locator(dates.YearLocator())
ax[0].xaxis.set_major_formatter(dates.DateFormatter("%Y"))
ax[0].tick_params(axis="x", which="major",rotation=0, pad=5)
ax[1].xaxis.set_major_locator(dates.YearLocator())
ax[1].xaxis.set_major_formatter(dates.DateFormatter("%Y"))
ax[1].tick_params(axis="x", which="major",rotation=0, pad=5)
ax[2].xaxis.set_major_locator(dates.YearLocator())
ax[2].xaxis.set_major_formatter(dates.DateFormatter("%Y"))
ax[2].tick_params(axis="x", which="major",rotation=0, pad=5)
ax[0].set_ylim((-0.5,1))
ax[1].set_ylim((-0.5,1))
ax[2].set_ylim((-0.5,1))
ax[0].legend(loc ='upper left')
ax[1].legend(loc = 'upper left')
ax[2].legend(loc = 'upper left')
plt.tight_layout()
plt.show();
# put everything into one graph
# plotting the relative change over time for the data
# set style
sns.set_style('darkgrid')
sns.set_palette('colorblind')
fig, ax = plt.subplots(figsize=(10,10), dpi=200)
ax.plot(data_ishares['IVV'].pct_change().cumsum(), label="iShares Core S&P 500", lw=2)
ax.plot(data_ishares['IEFA'].pct_change().cumsum(), label=" iShares Core MSCI EAFE", lw=2)
ax.plot(data_ishares['AGG'].pct_change().cumsum(), label="iShares Core U.S. Aggregate Bond",lw=2)
ax.plot(data_ishares['IJR'].pct_change().cumsum(), label="iShares Core S&P Small-Cap",lw=2)
ax.plot(data_amundi['SPX.MI'].pct_change().cumsum(), label="Lyxor S&P 500 UCITS", lw=2)
ax.plot(data_amundi['WLDC.MI'].pct_change().cumsum(), label="Lyxor MSCI World UCITS", lw=2)
ax.plot(data_amundi['USRI.PA'].pct_change().cumsum(), label="Amundi Index Solutions",lw=2)
ax.plot(data_amundi['MEUD.PA'].pct_change().cumsum(), label="Lyxor Core STOXX Europe 600",lw=2)
ax.plot(antons_data['URTH'].pct_change().cumsum(), label=" iShares MSCI World", lw=2)
ax.plot(antons_data['AEEM.PA'].pct_change().cumsum(), label="Amundi MSCI Emerging Markets", lw=2)
ax.plot(antons_data['IUHE.AS'].pct_change().cumsum(), label="iShares S&P 500 Health Care",lw=2)
ax.plot(antons_data['ICOM.L'].pct_change().cumsum(), label="iShares Diversified Commodity Swap",lw=2)
ax.set_xlabel('Time')
ax.set_ylabel('Adjusted Close')
ax.set_title('Relative change of all ETFs provided by iShares, Amundi & my own.')
fig.suptitle('Relative change for all 3 categories of ETFs', fontsize=20)
ax.xaxis.set_major_locator(dates.YearLocator())
ax.xaxis.set_major_formatter(dates.DateFormatter("%Y"))
ax.tick_params(axis="x", which="major",rotation=0, pad=5)
ax.set_ylim((-0.5,1))
plt.legend(loc = 'upper left')
plt.tight_layout()
plt.show();
# get a visible idea how these ETFs are correlated by focus on the development during the pandemic
sns.set_style('darkgrid')
sns.set_palette('colorblind')
fig, ax = plt.subplots(figsize=(10,10), dpi=200)
ax.plot(data_ishares['IVV']["2020-01-01":"2021-01-01"].pct_change().cumsum(), label="iShares Core S&P 500", lw=2)
ax.plot(data_ishares['IEFA']["2020-01-01":"2021-01-01"].pct_change().cumsum(), label="iShares Core MSCI EAFE", lw=2)
ax.plot(data_ishares['AGG']["2020-01-01":"2021-01-01"].pct_change().cumsum(), label="iShares Core U.S. Aggregate Bond",lw=2)
ax.plot(data_ishares['IJR']["2020-01-01":"2021-01-01"].pct_change().cumsum(), label="iShares Core S&P Small-Cap",lw=2)
ax.plot(data_amundi['SPX.MI']["2020-01-01":"2021-01-01"].pct_change().cumsum(), label="Lyxor S&P 500 UCITS", lw=2)
ax.plot(data_amundi['WLDC.MI']["2020-01-01":"2021-01-01"].pct_change().cumsum(), label="Lyxor MSCI World UCITS", lw=2)
ax.plot(data_amundi['USRI.PA']["2020-01-01":"2021-01-01"].pct_change().cumsum(), label="Amundi Index Solutions",lw=2)
ax.plot(data_amundi['MEUD.PA']["2020-01-01":"2021-01-01"].pct_change().cumsum(), label="Lyxor Core STOXX Europe 600",lw=2)
ax.plot(antons_data['URTH']["2020-01-01":"2021-01-01"].pct_change().cumsum(), label=" iShares MSCI World", lw=2)
ax.plot(antons_data['AEEM.PA']["2020-01-01":"2021-01-01"].pct_change().cumsum(), label="Amundi MSCI Emerging Markets", lw=2)
ax.plot(antons_data['IUHE.AS']["2020-01-01":"2021-01-01"].pct_change().cumsum(), label="iShares S&P 500 Health Care",lw=2)
ax.plot(antons_data['ICOM.L']["2020-01-01":"2021-01-01"].pct_change().cumsum(), label="iShares Diversified Commodity Swap",lw=2)
ax.set_xlabel('Time')
ax.set_ylabel('Adjusted Close')
ax.set_title('Relative change of all ETFs provided by iShares, Amundi & my own.')
fig.suptitle('Relative change for all 3 categories of ETFs during the pandemic', fontsize=20)
ax.xaxis.set_major_locator(dates.YearLocator())
ax.xaxis.set_major_formatter(dates.DateFormatter("%Y"))
ax.tick_params(axis="x", which="major",rotation=0, pad=5)
ax.set_ylim((-0.6,0.4))
plt.legend()
plt.tight_layout()
plt.show();
The zoomed graphs shows quite clearly, that the ETFs behave similarly. To get a furter insight into the behavior, we will have a look on their correlation.
# FINISH WHEN AMUNDI IS INCLUDED
all_data1 = data_ishares.merge(data_amundi, on="Date")
all_data = all_data1.merge(antons_data, on='Date')
all_data.head()
| IVV | IEFA | AGG | IJR | SPX.MI | WLDC.MI | USRI.PA | MEUD.PA | URTH | AEEM.PA | IUHE.AS | ICOM.L | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Date | ||||||||||||
| 2018-01-02 | 248.559158 | 57.914593 | 97.355820 | 72.191895 | 22.719999 | NaN | NaN | 149.320007 | 81.489555 | 4.2190 | NaN | 5.34 |
| 2018-01-03 | 250.000031 | 58.297379 | 97.364738 | 72.126610 | 22.920000 | NaN | NaN | 149.940002 | 81.976051 | 4.2489 | NaN | 5.33 |
| 2018-01-04 | 251.082840 | 58.871567 | 97.302307 | 72.322502 | 22.965000 | NaN | NaN | 151.339996 | 82.343201 | 4.2604 | NaN | 5.35 |
| 2018-01-05 | 252.670502 | 59.176064 | 97.239906 | 72.630341 | 23.129999 | NaN | NaN | 152.820007 | 82.994896 | 4.3045 | NaN | 5.30 |
| 2018-01-08 | 253.230316 | 59.132565 | 97.213142 | 72.742264 | 23.309999 | NaN | NaN | 153.089996 | 82.792976 | 4.3472 | NaN | 5.29 |
# Show the correlation
corr = all_data.corr()
corr.style.background_gradient(cmap='coolwarm')
| IVV | IEFA | AGG | IJR | SPX.MI | WLDC.MI | USRI.PA | MEUD.PA | URTH | AEEM.PA | IUHE.AS | ICOM.L | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| IVV | 1.000000 | 0.851226 | 0.554488 | 0.905307 | 0.967946 | 0.734649 | 0.964647 | 0.949259 | 0.991578 | 0.868632 | 0.944348 | 0.670173 |
| IEFA | 0.851226 | 1.000000 | 0.500347 | 0.914207 | 0.725692 | 0.424060 | 0.754676 | 0.888371 | 0.911360 | 0.944354 | 0.635533 | 0.486346 |
| AGG | 0.554488 | 0.500347 | 1.000000 | 0.306845 | 0.431514 | 0.322269 | 0.266694 | 0.408877 | 0.566613 | 0.534058 | -0.309803 | -0.180647 |
| IJR | 0.905307 | 0.914207 | 0.306845 | 1.000000 | 0.854104 | 0.617680 | 0.862288 | 0.935732 | 0.930844 | 0.896865 | 0.840981 | 0.712924 |
| SPX.MI | 0.967946 | 0.725692 | 0.431514 | 0.854104 | 1.000000 | 0.942311 | 0.997038 | 0.932708 | 0.933917 | 0.768360 | 0.935952 | 0.760221 |
| WLDC.MI | 0.734649 | 0.424060 | 0.322269 | 0.617680 | 0.942311 | 1.000000 | 0.922022 | 0.688353 | 0.609230 | 0.428212 | 0.679206 | -0.073265 |
| USRI.PA | 0.964647 | 0.754676 | 0.266694 | 0.862288 | 0.997038 | 0.922022 | 1.000000 | 0.923582 | 0.927526 | 0.775955 | 0.931995 | 0.809532 |
| MEUD.PA | 0.949259 | 0.888371 | 0.408877 | 0.935732 | 0.932708 | 0.688353 | 0.923582 | 1.000000 | 0.957954 | 0.873596 | 0.949918 | 0.724778 |
| URTH | 0.991578 | 0.911360 | 0.566613 | 0.930844 | 0.933917 | 0.609230 | 0.927526 | 0.957954 | 1.000000 | 0.912628 | 0.876815 | 0.635204 |
| AEEM.PA | 0.868632 | 0.944354 | 0.534058 | 0.896865 | 0.768360 | 0.428212 | 0.775955 | 0.873596 | 0.912628 | 1.000000 | 0.530977 | 0.499924 |
| IUHE.AS | 0.944348 | 0.635533 | -0.309803 | 0.840981 | 0.935952 | 0.679206 | 0.931995 | 0.949918 | 0.876815 | 0.530977 | 1.000000 | 0.756564 |
| ICOM.L | 0.670173 | 0.486346 | -0.180647 | 0.712924 | 0.760221 | -0.073265 | 0.809532 | 0.724778 | 0.635204 | 0.499924 | 0.756564 | 1.000000 |
# since thei graph is getting messy by adding more ETFs, a numeric comparison as reasonable
# therefore, the relativ change is shown as a number
ivv_pct = np.round((data_ishares['IVV'].pct_change().cumsum()[-1]) *100, 2)
iefa_pct = np.round((data_ishares['IEFA'].pct_change().cumsum()[-1]) *100, 2)
agg_pct = np.round((data_ishares['AGG'].pct_change().cumsum()[-1]) *100, 2)
ijr_pct = np.round((data_ishares['IJR'].pct_change().cumsum()[-1]) *100, 2)
spx_pct = np.round((data_amundi['SPX.MI'].pct_change().cumsum()[-1]) *100, 2)
wldc_pct = np.round((data_amundi['WLDC.MI'].pct_change().cumsum()[-1]) *100, 2)
usri_pct = np.round((data_amundi['USRI.PA'].pct_change().cumsum()[-1]) *100, 2)
meud_pct = np.round((data_amundi['MEUD.PA'].pct_change().cumsum()[-1]) *100, 2)
world_pct = np.round((antons_data['URTH'].pct_change().cumsum()[-1]) *100, 2)
em_pct = np.round((antons_data['AEEM.PA'].pct_change().cumsum()[-1]) *100, 2)
sp_hc_pct = np.round((antons_data['IUHE.AS'].pct_change().cumsum()[-1]) *100, 2)
com_pct = np.round((antons_data['ICOM.L'].pct_change().cumsum()[-1]) *100, 2)
pct_list = [ivv_pct, iefa_pct, agg_pct, ijr_pct, world_pct, em_pct, sp_hc_pct, com_pct, spx_pct, wldc_pct, usri_pct, meud_pct]
pct_own =[world_pct, em_pct, sp_hc_pct, com_pct]
pct_data = pd.DataFrame(pct_list, index=["iShares Core S&P 500 ETF", "iShares Core MSCI EAFE", "iShares Core U.S. Aggregate Bond"
, "iShares Core S&P Small-Cap", " iShares MSCI World", "Amundi MSCI Emerging Markets",
"iShares S&P 500 Health Care", "iShares Diversified Commodity Swap",
"Lyxor S&P 500 UCITS", "Lyxor MSCI World UCITS", "Amundi Index Solutions", "Lyxor Core STOXX Europe 600"], columns=["Relative change since 1-1-2018"])
pct_final = pct_data.sort_values('Relative change since 1-1-2018', ascending=False)
pct_data_own = pd.DataFrame(pct_own, index=[" iShares MSCI World", "Amundi MSCI Emerging Markets",
"iShares S&P 500 Health Care", "iShares Diversified Commodity Swap"], columns=["Relative change since 1-1-2018"])
pct_final_own = pct_data_own.sort_values('Relative change since 1-1-2018', ascending=False)
cm = sns.light_palette("green", as_cmap=True)
display(pct_final.style.background_gradient(cmap=cm))
cs = sns.light_palette("blue", as_cmap=True)
display(pct_final_own.style.background_gradient(cmap=cs))
| Relative change since 1-1-2018 | |
|---|---|
| Amundi Index Solutions | 63.060000 |
| Lyxor S&P 500 UCITS | 62.510000 |
| iShares Core S&P 500 ETF | 53.830000 |
| iShares Core S&P Small-Cap | 46.060000 |
| iShares Diversified Commodity Swap | 37.970000 |
| iShares MSCI World | 37.110000 |
| Lyxor Core STOXX Europe 600 | 28.780000 |
| iShares S&P 500 Health Care | 28.430000 |
| Amundi MSCI Emerging Markets | 12.820000 |
| iShares Core MSCI EAFE | 8.020000 |
| Lyxor MSCI World UCITS | 4.090000 |
| iShares Core U.S. Aggregate Bond | -2.160000 |
| Relative change since 1-1-2018 | |
|---|---|
| iShares Diversified Commodity Swap | 37.970000 |
| iShares MSCI World | 37.110000 |
| iShares S&P 500 Health Care | 28.430000 |
| Amundi MSCI Emerging Markets | 12.820000 |
# Step 2 - show the key fact for every ETF
# to finalize: use pd.concat instead & get rid off the column names
# # can we exclude index & column names?
for i in ticker_ishares:
print(i)
key_facts = yf.Ticker(i).institutional_holders
key_facts = pd.concat([key_facts, yf.Ticker(i).major_holders], axis = 0)
key_facts = key_facts.set_index([0])
display(key_facts)
for i in ticker_amundi:
print(i)
key_facts = yf.Ticker(i).institutional_holders
key_facts = pd.concat([key_facts, yf.Ticker(i).major_holders], axis = 1)
key_facts = key_facts.set_index([0])
display(key_facts)
for i in antons_ticker:
print(i)
key_facts = yf.Ticker(i).institutional_holders
key_facts = pd.concat([key_facts, yf.Ticker(i).major_holders], axis = 0)
key_facts = key_facts.set_index([0])
display(key_facts)
IVV
| 1 | |
|---|---|
| 0 | |
| Net Assets | 268.45B |
| NAV | 386.19 |
| PE Ratio (TTM) | 2.75 |
| Yield | 1.72% |
| YTD Daily Total Return | -17.72% |
| Beta (5Y Monthly) | 1.00 |
| Expense Ratio (net) | 0.03% |
| Inception Date | 2000-05-15 |
| Previous Close | 386.25 |
| Open | 385.58 |
| Bid | 383.89 x 800 |
| Ask | 383.90 x 800 |
| Day's Range | 382.98 - 385.60 |
| 52 Week Range | 349.53 - 482.07 |
| Volume | 1498483 |
| Avg. Volume | 4875575 |
IEFA
| 1 | |
|---|---|
| 0 | |
| Net Assets | 75.57B |
| NAV | 56.21 |
| PE Ratio (TTM) | NaN |
| Yield | 5.16% |
| YTD Daily Total Return | -23.94% |
| Beta (5Y Monthly) | 1.04 |
| Expense Ratio (net) | 0.07% |
| Inception Date | 2012-10-18 |
| Previous Close | 56.21 |
| Open | 56.28 |
| Bid | 56.09 x 21500 |
| Ask | 56.10 x 1800 |
| Day's Range | 56.04 - 56.29 |
| 52 Week Range | 51.34 - 77.91 |
| Volume | 16366541 |
| Avg. Volume | 13305853 |
AGG
| 1 | |
|---|---|
| 0 | |
| Net Assets | 77.93B |
| NAV | 94.78 |
| PE Ratio (TTM) | 121.23 |
| Yield | 2.16% |
| YTD Daily Total Return | -15.61% |
| Beta (5Y Monthly) | 1.00 |
| Expense Ratio (net) | 0.03% |
| Inception Date | 2003-09-22 |
| Previous Close | 94.86 |
| Open | 94.98 |
| Bid | 95.03 x 4000 |
| Ask | 95.04 x 1000 |
| Day's Range | 94.88 - 95.07 |
| 52 Week Range | 93.20 - 115.50 |
| Volume | 3230840 |
| Avg. Volume | 7105759 |
IJR
| 1 | |
|---|---|
| 0 | |
| Net Assets | 59.05B |
| NAV | 98.30 |
| PE Ratio (TTM) | 2.52 |
| Yield | 1.91% |
| YTD Daily Total Return | -13.75% |
| Beta (5Y Monthly) | 1.10 |
| Expense Ratio (net) | 0.06% |
| Inception Date | 2000-05-22 |
| Previous Close | 98.31 |
| Open | 97.71 |
| Bid | 96.75 x 800 |
| Ask | 96.76 x 800 |
| Day's Range | 96.46 - 97.71 |
| 52 Week Range | 86.40 - 121.45 |
| Volume | 1627794 |
| Avg. Volume | 3966312 |
SPX.MI
| 1 | 1 | |
|---|---|---|
| 0 | ||
| (Net Assets, Previous Close) | NaN | 40.12 |
| (NAV, Open) | 39.08 | 40.07 |
| (PE Ratio (TTM), Bid) | NaN | 39.76 x 0 |
| (Yield, Ask) | 1.47% | 39.82 x 0 |
| (YTD Daily Total Return, Day's Range) | -7.96% | 39.78 - 40.07 |
| (Beta (5Y Monthly), 52 Week Range) | 0.97 | 35.92 - 43.65 |
| (Expense Ratio (net), Volume) | 0.09% | 44691 |
| (Inception Date, Avg. Volume) | 2010-03-26 | 10818 |
WLDC.MI
| 1 | 1 | |
|---|---|---|
| 0 | ||
| (Net Assets, Previous Close) | NaN | 10.82 |
| (NAV, Open) | 10.76 | 10.79 |
| (PE Ratio (TTM), Bid) | NaN | 10.72 x N/A |
| (Yield, Ask) | NaN | 10.75 x N/A |
| (YTD Daily Total Return, Day's Range) | -9.90% | 10.72 - 10.79 |
| (Beta (5Y Monthly), 52 Week Range) | 0.00 | 9.86 - 11.91 |
| (Expense Ratio (net), Volume) | 0.30% | 1388 |
| (Inception Date, Avg. Volume) | 2021-06-02 | 3893 |
USRI.PA
| 1 | 1 | |
|---|---|---|
| 0 | ||
| (Net Assets, Previous Close) | NaN | 88.87 |
| (NAV, Open) | 88.58 | 88.82 |
| (PE Ratio (TTM), Bid) | NaN | 0.00 x 0 |
| (Yield, Ask) | 0.00% | 0.00 x 0 |
| (YTD Daily Total Return, Day's Range) | -8.47% | 88.03 - 88.82 |
| (Beta (5Y Monthly), 52 Week Range) | 0.97 | 78.38 - 98.40 |
| (Expense Ratio (net), Volume) | 0.08% | 8686 |
| (Inception Date, Avg. Volume) | 2018-09-11 | 12653 |
MEUD.PA
| 1 | 1 | |
|---|---|---|
| 0 | ||
| (Net Assets, Previous Close) | 34.44M | 182.41 |
| (NAV, Open) | 180.43 | 183.27 |
| (PE Ratio (TTM), Bid) | NaN | 0.00 x 0 |
| (Yield, Ask) | 0.00% | 0.00 x 0 |
| (YTD Daily Total Return, Day's Range) | -13.70% | 181.89 - 183.29 |
| (Beta (5Y Monthly), 52 Week Range) | 1.02 | 166.94 - 213.07 |
| (Expense Ratio (net), Volume) | 0.15% | 828 |
| (Inception Date, Avg. Volume) | 2013-04-03 | 13427 |
URTH
| 1 | |
|---|---|
| 0 | |
| Net Assets | 1.88B |
| NAV | 107.30 |
| PE Ratio (TTM) | NaN |
| Yield | 2.04% |
| YTD Daily Total Return | -19.96% |
| Beta (5Y Monthly) | 1.03 |
| Expense Ratio (net) | 0.24% |
| Inception Date | 2012-01-10 |
| Previous Close | 107.44 |
| Open | 107.39 |
| Bid | 106.87 x 900 |
| Ask | 106.91 x 900 |
| Day's Range | 106.65 - 107.15 |
| 52 Week Range | 97.44 - 136.75 |
| Volume | 110790 |
| Avg. Volume | 314506 |
AEEM.PA
| 1 | |
|---|---|
| 0 | |
| Net Assets | NaN |
| NAV | 4.09 |
| PE Ratio (TTM) | NaN |
| Yield | 0.00% |
| YTD Daily Total Return | -19.64% |
| Beta (5Y Monthly) | 1.00 |
| Expense Ratio (net) | 0.10% |
| Inception Date | 2018-04-18 |
| Previous Close | 4.1855 |
| Open | 4.2118 |
| Bid | 0.0000 x 0 |
| Ask | 0.0000 x 0 |
| Day's Range | 4.1650 - 4.2118 |
| 52 Week Range | 4.0259 - 5.2850 |
| Volume | 121967 |
| Avg. Volume | 223479 |
IUHE.AS
| 1 | |
|---|---|
| 0 | |
| Net Assets | NaN |
| NAV | 6.53 |
| PE Ratio (TTM) | NaN |
| Yield | 1.36% |
| YTD Daily Total Return | -7.36% |
| Beta (5Y Monthly) | 0.00 |
| Expense Ratio (net) | 0.15% |
| Inception Date | 2020-06-15 |
| Previous Close | 6.51 |
| Open | 6.54 |
| Bid | N/A x N/A |
| Ask | N/A x N/A |
| Day's Range | 6.51 - 6.54 |
| 52 Week Range | 5.94 - 7.20 |
| Volume | 448 |
| Avg. Volume | 1235 |
ICOM.L
| 1 | |
|---|---|
| 0 | |
| Net Assets | 1.56B |
| NAV | 7.15 |
| PE Ratio (TTM) | NaN |
| Yield | 0.00% |
| YTD Daily Total Return | 13.27% |
| Beta (5Y Monthly) | 0.00 |
| Expense Ratio (net) | 0.19% |
| Inception Date | 2017-07-18 |
| Previous Close | 7.13 |
| Open | 7.19 |
| Bid | 7.20 x 0 |
| Ask | 7.21 x 0 |
| Day's Range | 7.13 - 7.23 |
| 52 Week Range | 5.90 - 8.65 |
| Volume | 407562 |
| Avg. Volume | 570475 |
Conclusion Step 2:
Two interesting perspectives: how does the optimal weighting for my own assets looks like & how does the optimal weighting for all assets looks like.
# compute the daily returns for each ETF
# since I could not figure out how to clean the data for some ETFs appropiately, I excluded them until I found a solution
all_data = all_data.drop(['WLDC.MI', 'USRI.PA', 'IUHE.AS', 'ICOM.L'], axis=1)
all_data_returns = all_data.pct_change().dropna()
# compute equal weighting
N = len(all_data_returns.columns)
equal_weights = N * [1/N]
# compute the cummulative returns
equal_returns = np.dot(equal_weights,all_data_returns.transpose())
cum_equal_returns = (1 + equal_returns).cumprod() - 1
cum_equal_returns_perc = pd.Series(100 * cum_equal_returns)
cum_equal_returns_perc.index = all_data_returns.index
# plot them
sns.set_style('darkgrid')
sns.set_palette('colorblind')
plt.figure(figsize=(10,4), dpi=150)
plt.plot(cum_equal_returns_perc)
plt.xlabel('Time')
plt.ylabel('Cummulative Return')
plt.title('Cummulative Return of all ETFs provided by iShares, Amundi & my own with equal weighting.')
plt.tight_layout()
plt.show();
Erläuterung Efficient Frontier: The EF shows the set of optimal portfolios that offer the highest expected return for a given risk level or the lowest risk for a given level of expected return.
# mean-variance method with all data
mu = mean_historical_return(all_data)
S = CovarianceShrinkage(all_data).ledoit_wolf()
ef = EfficientFrontier(mu, S)
weights = ef.max_sharpe()
cleaned_weights = ef.clean_weights()
print(dict(cleaned_weights))
{'IVV': 0.07373, 'IEFA': 0.0, 'AGG': 0.0, 'IJR': 0.0, 'SPX.MI': 0.92627, 'MEUD.PA': 0.0, 'URTH': 0.0, 'AEEM.PA': 0.0}
ef.portfolio_performance(verbose=True)
Expected annual return: 11.8% Annual volatility: 18.4% Sharpe Ratio: 0.53
(0.11812858236785169, 0.18379300475950164, 0.533908145722171)
ef_plot = EfficientFrontier(mu, S)
plotting.plot_efficient_frontier(ef_plot)
<AxesSubplot:xlabel='Volatility', ylabel='Return'>
# plot development with the efficient frontier weighting
ef_weights = [0.01692, 0.0, 0.0, 0.0, 0.98308, 0.0, 0.0, 0.0]
ef_returns = np.dot(ef_weights, all_data_returns.transpose())
cum_ef_returns = (1 + ef_returns).cumprod() - 1
cum_ef_returns_perc = pd.Series(100 * cum_ef_returns)
cum_ef_returns_perc.index = all_data_returns.index
# plot them
sns.set_style('darkgrid')
sns.set_palette('colorblind')
plt.figure(figsize=(10,4), dpi=150)
plt.plot(cum_ef_returns_perc)
plt.xlabel('Time')
plt.ylabel('Cummulative Return')
plt.title('Cummulative Return of all ETFs provided by iShares, Amundi & my own with efficient frontier weighting.')
plt.tight_layout()
plt.show();
# constraints: the weighting of asset 1 must be greater or equal to 20%, weighting of asset 3 must be equal to 15%
# and the weighting of asset 4 and 5 must be added to be smaller or equal than 10%
ef_plot_c1 = EfficientFrontier(mu, S)
ef_plot_c1.add_constraint(lambda w: w[0] >= 0.2)
ef_plot_c1.add_constraint(lambda w: w[2] == 0.15)
ef_plot_c1.add_constraint(lambda w: w[3] + w[4] <= 0.10)
plotting.plot_efficient_frontier(ef_plot_c1)
<AxesSubplot:xlabel='Volatility', ylabel='Return'>
# mean-variance method my own
mu2 = mean_historical_return(antons_data)
S2 = CovarianceShrinkage(antons_data).ledoit_wolf()
ef2 = EfficientFrontier(mu2, S2)
weights2 = ef2.max_sharpe()
cleaned_weights2 = ef2.clean_weights()
print(dict(cleaned_weights2))
antons_data_returns = antons_data.pct_change().dropna()
ef_weights2 = [0.0, 0.0, 0.83616, 0.16384]
ef2_returns = np.dot(ef_weights2, antons_data_returns.transpose())
cum_ef2_returns = (1 + ef2_returns).cumprod() - 1
cum_ef2_returns_perc = pd.Series(100 * cum_ef2_returns)
cum_ef2_returns_perc.index = antons_data_returns.index
{'URTH': 0.0, 'AEEM.PA': 0.0, 'IUHE.AS': 0.82098, 'ICOM.L': 0.17902}
sns.set_style('darkgrid')
sns.set_palette('colorblind')
fig, ax = plt.subplots(figsize=(10,10), dpi=200)
ax.plot(cum_equal_returns_perc, label="equal", lw=2)
ax.plot(cum_ef_returns_perc, label="ef_all", lw=2)
ax.plot(cum_ef2_returns_perc, label="ef_own",lw=2)
ax.set_xlabel('Time')
ax.set_ylabel('Relative change')
fig.suptitle('Relative change for all 3 categories of ETFs using equal and efficient frontier weighting', fontsize=20)
ax.xaxis.set_major_locator(dates.YearLocator())
ax.xaxis.set_major_formatter(dates.DateFormatter("%Y"))
ax.tick_params(axis="x", which="major",rotation=0, pad=5)
ax.legend(loc ='upper left')
plt.tight_layout()
plt.show();
Conslusion Portfolio Optimization: it is clearly shown, that my past decisions lead to missing opportunities or missing growth, resp. Therefore, my choices were rather bad.
Design a interactive dashboard including the findings from step 1 to 3. This step will be designed in another python file.